package com.jevons.geo_lib.geopackage;

import android.content.ContentValues;
import android.content.Context;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.jevons.geo_lib.geojson.MapboxJsonHelper;
import com.jevons.geo_lib.utils.MapDataUtils;
import com.jevons.geo_lib.utils.StringUtils;
import com.mapbox.geojson.Feature;
import com.mapbox.geojson.Geometry;
import com.mapbox.geojson.GeometryCollection;

import java.io.File;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import io.reactivex.Observable;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import mil.nga.geopackage.GeoPackage;
import mil.nga.geopackage.GeoPackageException;
import mil.nga.geopackage.GeoPackageFactory;
import mil.nga.geopackage.GeoPackageManager;
import mil.nga.geopackage.contents.Contents;
import mil.nga.geopackage.contents.ContentsDao;
import mil.nga.geopackage.contents.ContentsDataType;
import mil.nga.geopackage.db.GeoPackageDataType;
import mil.nga.geopackage.extension.CrsWktExtension;
import mil.nga.geopackage.features.columns.GeometryColumns;
import mil.nga.geopackage.features.columns.GeometryColumnsDao;
import mil.nga.geopackage.features.index.FeatureIndexManager;
import mil.nga.geopackage.features.index.FeatureIndexType;
import mil.nga.geopackage.features.user.FeatureColumn;
import mil.nga.geopackage.features.user.FeatureDao;
import mil.nga.geopackage.features.user.FeatureRow;
import mil.nga.geopackage.features.user.FeatureTable;
import mil.nga.geopackage.geom.GeoPackageGeometryData;
import mil.nga.geopackage.srs.SpatialReferenceSystem;
import mil.nga.geopackage.srs.SpatialReferenceSystemDao;
import mil.nga.geopackage.user.custom.UserCustomColumn;
import mil.nga.geopackage.user.custom.UserCustomRow;
import mil.nga.sf.GeometryType;
import mil.nga.sf.proj.ProjectionConstants;
import mil.nga.sf.wkt.GeometryReader;

/**
 * Copyright 2020 JxGIS
 *
 * @file WorkGeoManger
 * @auth linaisheng
 * Created on 2023/6/2.
 * Description：
 *
 */
public class GeometryOperationBackupsManager {


    public static final String GEOPACKAGE_DB_NAME = "geometry_operation_backups_db";

    public static final String TABLE_WORK_GEOMETRY = "geometry_operation_backups";

    private static final String FID_COLUMN = "fid";
    private static final String ID_COLUMN = "id";


    private static final String OPERATION_TYPE = "operation_type";
    private static final String MODULE_FLAG = "module_flag";

    private static final String ORIGIN_WKT = "origin_wkt";
    private static final String RESULT_WKT = "result_wkt";

    private static final String USER_ID = "userId";
    private static final String USER_NAME = "userName";
    private static final String CREATE_TIME = "create_time";

    private static final String GEOMETRY_COLUMN = "geometry";


    private GeoPackage mGeoPackage;
    protected Gson gson = new Gson();

    private FeatureDao geometryOperationDao;


    public GeometryOperationBackupsManager(Context context, File fileDb) {
        if (fileDb.exists()) {
            try {
                GeoPackageManager manager = GeoPackageFactory.getManager(context);
                GeoPackage geoPackage = manager.open(GEOPACKAGE_DB_NAME);
                if (geoPackage == null) {
                    throw new GeoPackageException("Failed to open database");
                }
                mGeoPackage = geoPackage;

                geometryOperationDao = mGeoPackage.getFeatureDao(TABLE_WORK_GEOMETRY);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            throw new GeoPackageException("database file is no exist");
        }
    }

    public boolean createOperationRow(
            String featureId, String moduleFlag, String operationType,
            Geometry originGeometry, Geometry result, String userId, String userName) {
        return createOperationRow(featureId, moduleFlag, operationType,
                originGeometry, MapDataUtils.jsonToWkt(MapboxJsonHelper.geometryToJson(result)),
                userId, userName);
    }

    public boolean createOperationRow(
            String featureId, String moduleFlag, String operationType,
            List<Geometry> originGeometryList, Geometry result, String userId, String userName) {
        GeometryCollection collection = GeometryCollection.fromGeometries(originGeometryList);
        return createOperationRow(featureId, moduleFlag, operationType,
                collection, MapDataUtils.jsonToWkt(MapboxJsonHelper.geometryToJson(result)),
                userId, userName);
    }

    public boolean createOperationRow(
            String featureId, String moduleFlag, String operationType,
            Geometry originGeometry, List<Feature> resultList, String userId, String userName) {
        List<Geometry> geometries = new ArrayList<>();
        for (Feature feature : resultList) {
            geometries.add(feature.geometry());
        }
        GeometryCollection collection = GeometryCollection.fromGeometries(geometries);
        return createOperationRow(featureId, moduleFlag, operationType,
                originGeometry, MapDataUtils.jsonToWkt(MapboxJsonHelper.geometryToJson(collection)),
                userId, userName);
    }


    protected boolean createOperationRow(
            String featureId, String moduleFlag, String operationType,
            Geometry originGeometry, String resultWkt, String userId, String userName) {
        try {
            FeatureRow featureRow = geometryOperationDao.newRow();

            featureRow.setValue(ID_COLUMN, featureId);
            featureRow.setValue(OPERATION_TYPE, operationType);
            featureRow.setValue(MODULE_FLAG, moduleFlag);

            if (originGeometry != null) {
                String originWkt = MapDataUtils.jsonToWkt(MapboxJsonHelper.geometryToJson(originGeometry));
                featureRow.setValue(ORIGIN_WKT, originWkt);

                GeometryReader reader = new GeometryReader(originWkt);
                GeoPackageGeometryData geomData = new GeoPackageGeometryData(reader.read());
                featureRow.setGeometry(geomData);
            }
            featureRow.setValue(RESULT_WKT, resultWkt);

            featureRow.setValue(USER_ID, userId);
            featureRow.setValue(USER_NAME, userName);

            featureRow.setValue(CREATE_TIME, StringUtils.getCurrentTime());

            boolean result = geometryOperationDao.insert(featureRow) > 0;
            return result;
        } catch (Exception e) {

        }
        return false;
    }


    public static int checkExistDbFileAndCreate(Context context, File fileDb) {
        if (!fileDb.exists()) {
            GeoPackageManager manager = GeoPackageFactory.getManager(context);
            manager.delete(GEOPACKAGE_DB_NAME);
            manager.createFile(GEOPACKAGE_DB_NAME, fileDb);

            GeoPackage geoPackage = manager.open(GEOPACKAGE_DB_NAME);
            if (geoPackage == null) {
                throw new GeoPackageException("Failed to open database");
            } else {
                try {
                    //新增坐标系
                    createCrsWktExtension(geoPackage);
                    //创建图形表
                    createGeometryTable(geoPackage);
                    //创建索引
                    createGeometryIndexExtension(context, geoPackage);

                    geoPackage.close();
                } catch (Exception e){
                    e.printStackTrace();
                }

                return 2;
            }
        } else {
            return 1;
        }

    }


    //新增4490坐标系
    private static void createCrsWktExtension(GeoPackage geoPackage)
            throws SQLException {

        CrsWktExtension wktExtension = new CrsWktExtension(geoPackage);
        wktExtension.getOrCreate();

        SpatialReferenceSystemDao srsDao = geoPackage
                .getSpatialReferenceSystemDao();

        SpatialReferenceSystem srs4490 = new SpatialReferenceSystem();
        srs4490.setSrsName("epsg:4490");
        srs4490.setSrsId(4490);
        srs4490.setOrganization(ProjectionConstants.AUTHORITY_EPSG);
        srs4490.setOrganizationCoordsysId(srs4490.getSrsId());
        srs4490.setDefinition("GEOGCS[\"China Geodetic Coordinate System 2000\", \n" +
                "  DATUM[\"China 2000\", \n" +
                "    SPHEROID[\"CGCS2000\", 6378137.0, 298.257222101, AUTHORITY[\"EPSG\",\"1024\"]], \n" +
                "    AUTHORITY[\"EPSG\",\"1043\"]], \n" +
                "  PRIMEM[\"Greenwich\", 0.0, AUTHORITY[\"EPSG\",\"8901\"]], \n" +
                "  UNIT[\"degree\", 0.017453292519943295], \n" +
                "  AXIS[\"Geodetic longitude\", EAST], \n" +
                "  AXIS[\"Geodetic latitude\", NORTH], \n" +
                "  AUTHORITY[\"EPSG\",\"4490\"]]");
        srs4490.setDescription("epsg:4490");
        srsDao.create(srs4490);

    }

    private static void createGeometryTable(GeoPackage geoPackage)
            throws SQLException {
        SpatialReferenceSystemDao srsDao = geoPackage
                .getSpatialReferenceSystemDao();

        SpatialReferenceSystem srs = srsDao.getOrCreateCode(
                ProjectionConstants.AUTHORITY_EPSG, (long) 4490);

        geoPackage.createGeometryColumnsTable();

        ContentsDao contentsDao = geoPackage.getContentsDao();
        Contents contents = new Contents();
        contents.setTableName(TABLE_WORK_GEOMETRY);
        contents.setDataType(ContentsDataType.FEATURES);
        contents.setIdentifier(TABLE_WORK_GEOMETRY);
        contents.setDescription(TABLE_WORK_GEOMETRY + " table");
        contents.setMinX(0d);
        contents.setMinY(0d);
        contents.setMaxX(180d);
        contents.setMaxY(90d);
        contents.setSrs(srs);

        List<FeatureColumn> columns = new ArrayList<FeatureColumn>();

        columns.add(FeatureColumn.createPrimaryKeyColumn(FID_COLUMN));
        columns.add(FeatureColumn.createColumn(ID_COLUMN,
                GeoPackageDataType.TEXT, true, ""));

        columns.add(FeatureColumn.createColumn(MODULE_FLAG,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(OPERATION_TYPE,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(ORIGIN_WKT,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(RESULT_WKT,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(USER_ID,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(USER_NAME,
                GeoPackageDataType.TEXT, false, ""));
        columns.add(FeatureColumn.createColumn(CREATE_TIME,
                GeoPackageDataType.TEXT, false, ""));

        columns.add(FeatureColumn.createGeometryColumn(GEOMETRY_COLUMN, GeometryType.GEOMETRY));

        FeatureTable table = new FeatureTable(TABLE_WORK_GEOMETRY, columns);
        geoPackage.createFeatureTable(table);

        contentsDao.create(contents);

        GeometryColumnsDao geometryColumnsDao = geoPackage
                .getGeometryColumnsDao();

        GeometryColumns geometryColumns = new GeometryColumns();
        geometryColumns.setContents(contents);
        geometryColumns.setColumnName(GEOMETRY_COLUMN);
        geometryColumns.setGeometryType(GeometryType.GEOMETRY);
        geometryColumns.setSrs(srs);
        geometryColumns.setZ((byte) 0);
        geometryColumns.setM((byte) 0);
        geometryColumnsDao.create(geometryColumns);

    }


    private static void createGeometryIndexExtension(Context context, GeoPackage geoPackage) {
        List<String> featureTables = geoPackage.getFeatureTables();
        for (String featureTable : featureTables) {

            FeatureDao featureDao = geoPackage.getFeatureDao(featureTable);
            FeatureIndexManager indexer = new FeatureIndexManager(context, geoPackage, featureDao);
            indexer.setIndexLocation(FeatureIndexType.GEOPACKAGE);
            indexer.index();
            indexer.close();
        }

    }

    protected UserCustomRow getLocalRowData(UserCustomRow customRow, JsonObject property) {
        Iterator<Map.Entry<String, JsonElement>> iterator = property.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, JsonElement> entry = iterator.next();
            if (!customRow.hasColumn(entry.getKey())) {
                continue;
            }
            UserCustomColumn column = customRow.getColumn(entry.getKey());

            JsonElement element = entry.getValue();
            if (element == null) {
                customRow.setValue(entry.getKey(), null);
            } else if (element instanceof JsonNull) {
                customRow.setValue(entry.getKey(), null);
            } else {
                Object object = getUserColumnValue(column, entry.getValue().getAsString());
                customRow.setValue(entry.getKey(), object);
            }
        }
        return customRow;
    }

    protected Object getUserColumnValue(UserCustomColumn column, String value) {
        GeoPackageDataType dataType = column.getDataType();
        if (StringUtils.isEmpty(value)) {
            return null;
        }

        if (dataType == GeoPackageDataType.TEXT) {
            return value;
        } else if (dataType == GeoPackageDataType.BOOLEAN) {
            return Boolean.parseBoolean(value);
        } else if (dataType == GeoPackageDataType.TINYINT) {
            return Byte.parseByte(value);
        } else if (dataType == GeoPackageDataType.SMALLINT) {
            return Short.parseShort(value);
        } else if (dataType == GeoPackageDataType.MEDIUMINT) {
            return Integer.parseInt(value);
        } else if (dataType == GeoPackageDataType.INT || dataType == GeoPackageDataType.INTEGER) {
            return Long.parseLong(value);
        } else if (dataType == GeoPackageDataType.FLOAT) {
            return Float.parseFloat(value);
        } else if (dataType == GeoPackageDataType.REAL || dataType == GeoPackageDataType.DOUBLE) {
            return Double.parseDouble(value);
        } else if (dataType == GeoPackageDataType.DATE || dataType == GeoPackageDataType.DATETIME) {
            return value;
        }
        return null;
    }

    protected  <T> Observable<T> createObservable(ObservableOnSubscribe<T> subscribe) {
        return Observable.create(subscribe)
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    protected JsonObject valuesToJsonObject(UserCustomRow row) {
        JsonObject jsonObject = new JsonObject();

        ContentValues values = row.toContentValues();
        Iterator<String> iterator = values.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            GeoPackageDataType dataType = row.getColumn(key).getDataType();
            if (dataType == GeoPackageDataType.BOOLEAN) {
                jsonObject.addProperty(key, values.getAsBoolean(key));
            } else if (dataType == GeoPackageDataType.TINYINT) {
                jsonObject.addProperty(key, values.getAsByte(key));
            } else if (dataType == GeoPackageDataType.SMALLINT) {
                jsonObject.addProperty(key, values.getAsShort(key));
            } else if (dataType == GeoPackageDataType.MEDIUMINT) {
                jsonObject.addProperty(key, values.getAsInteger(key));
            } else if (dataType == GeoPackageDataType.INT) {
                jsonObject.addProperty(key, values.getAsLong(key));
            } else if (dataType == GeoPackageDataType.INTEGER) {
                jsonObject.addProperty(key, values.getAsLong(key));
            } else if (dataType == GeoPackageDataType.FLOAT) {
                jsonObject.addProperty(key, values.getAsFloat(key));
            } else if (dataType == GeoPackageDataType.DOUBLE) {
                jsonObject.addProperty(key, values.getAsDouble(key));
            } else if (dataType == GeoPackageDataType.REAL) {
                jsonObject.addProperty(key, values.getAsDouble(key));
            } else if (dataType == GeoPackageDataType.TEXT) {
                jsonObject.addProperty(key, values.getAsString(key));
            } else if (dataType == GeoPackageDataType.BLOB) {
//                jsonObject.addProperty(key, values.getAsByteArray(key));
            } else if (dataType == GeoPackageDataType.DATE) {
                jsonObject.addProperty(key, values.getAsString(key));
            } else if (dataType == GeoPackageDataType.DATETIME) {
                jsonObject.addProperty(key, values.getAsString(key));
            }
        }

        return jsonObject;
    }

    public void onDestroy() {
        geometryOperationDao = null;
        if (mGeoPackage != null) {
            mGeoPackage.close();
            mGeoPackage = null;
        }
    }
}